/**
 * Raphael.js 文档: https://dmitrybaranovskiy.github.io/raphael/
 */

import { AppModel } from '../model/AppModel';
import { PartFigure, SnapStepPart } from './PartFigure';
import { PartBase } from '../model/parts/PartBase';
import { EventEmitter } from '../base/events';
import { PortDragHandle } from './PortDragHandle';
import { StudFigure } from './StudFigure';
import { WireFigure } from './WireFigure';
import { Wire } from '../model/conn/Wire';
import { FigureBase } from './FigureBase';
import {BeCore16Figure, BeCore32Figure} from './BeCoreFigure';
import ServoFigure from './ServoFigure';
import { testBBox } from './util';
import LEDFigure from './LEDFigure';

export const PPM = 5

const PaperWidth = 2970
const PaperHeight = 2100
const ZoomStep = 0.1

const mapPartFigureClasses: { [key:string]: new (canvas: Canvas, part: PartBase)=>PartFigure } = {
    "BeCore16": BeCore16Figure ,
    "BeCore32": BeCore32Figure ,
    "BeServo": ServoFigure ,
    "BeMotor": ServoFigure ,
    "BeLED": LEDFigure ,
}

export class Canvas extends EventEmitter {
    
    model?: AppModel;
    
    paper: any

    public portDragHandle?: PortDragHandle

    public activeFigure: FigureBase | undefined

    public paperElement: any
    
    public readonly MaxPaperScale = 10          // 放大
    public readonly MinPaperScale = 0.2         // 缩小

    // public totalAssets: raphaeljs.Set

    private mousewheelHandle: any
    private touchmoveHanlde: any

    init(svgId: string) {

        this.paper = Raphael(svgId, PaperWidth, PaperHeight)
        this.paperElement = $("#"+svgId)[0]

        this.portDragHandle = new PortDragHandle(this)
        
        this.paper.viewbox = {
            x:0, y:0,
            scale: 1
        }

        // 滚动缩放
        this.mousewheelHandle = (event: any)=>{
            event.stopPropagation()
                
            let scale = 1
            if(event.deltaY<0) {
                scale = this.paper.viewbox.scale-ZoomStep
            }
            // 缩小
            else {
                scale = this.paper.viewbox.scale+ZoomStep
            }

            this.scale(scale, event.offsetX, event.offsetY)

            return false
        }
        $(this.paperElement).mousewheel(this.mousewheelHandle)

        // 两指撮合缩放
        let touchedDis = -1
        let lastTouchedTime = -1
        this.touchmoveHanlde = (event: any)=>{
            if(event.originalEvent.touches.length!=2){
                return
            }
            
            // 阻止移动操作
            event.originalEvent.preventDefault()
            event.originalEvent.stopPropagation()
            
            if(event.originalEvent.changedTouches.length!=2){
                return
            }
            
            let p1 = event.originalEvent.changedTouches[0]
            let p2 = event.originalEvent.changedTouches[1]

            let x = p2.clientX - p1.clientX
            let y = p2.clientY - p1.clientY
            let dis = Math.sqrt(x*x + y*y)

            let now = Date.now()

            if(touchedDis>0 && lastTouchedTime>0 && now-lastTouchedTime<200 ) {
                let zoom = dis - touchedDis
                if(Math.abs(zoom)>1) {
                    x = p1.clientX + x/2
                    y = p1.clientY + y/2
                    this.scale( this.paper.viewbox.scale + (zoom>1? 0.02: -0.02), x, y )
                }
            }

            lastTouchedTime = now
            touchedDis = dis

        }
        $(this.paperElement).on('touchmove', this.touchmoveHanlde)

        this.createBg()
    }

    createBg() {
        let bg = this.paper.rect(0,0, PaperWidth, PaperHeight)
            .attr({fill: "#fafafa", "stroke-width": 0})

        // 移动整个 paper
        let lastPos = [0,0]
        bg.drag(
            (dx:number, dy:number, x:number, y:number)=>{
                this.paper.viewbox.x-= (x - lastPos[0])/this.paper.viewbox.scale
                this.paper.viewbox.y-= (y - lastPos[1])/this.paper.viewbox.scale

                lastPos[0] = x
                lastPos[1] = y
                this.updateViewBox()
            }
            , (x:number, y:number) => {
                lastPos[0] = x
                lastPos[1] = y
            }
        )

        // 处理选择操作
        bg.click(()=>{
            this.setActiveFigure()
        })

        bg.toBack()
    }

    updateViewBox () {
        // if(this.paper.viewbox.x<-1000)
        //     this.paper.viewbox.x = -1000
        // if(this.paper.viewbox.y<-1000)
        //     this.paper.viewbox.y = -1000
        this.paper.setViewBox(this.paper.viewbox.x, this.paper.viewbox.y, PaperWidth/this.paper.viewbox.scale, PaperHeight/this.paper.viewbox.scale)
    }
    public scale(scale: number, offsetX?: number, offsetY?: number, slient=false) {
        if(!offsetX) {
            offsetX = this.paperElement.clientWidth/2
        }
        if(!offsetY) {
            offsetY = this.paperElement.clientHeight/2
        }

        let px = offsetX - this.paperElement.offsetLeft
        let py = offsetY - this.paperElement.offsetTop

        let pivotX = this.paper.viewbox.x + px/this.paper.viewbox.scale
        let pivotY = this.paper.viewbox.y + py/this.paper.viewbox.scale

        let oldScale = this.paper.viewbox.scale

        // 放大
        if(scale<this.MinPaperScale) {
            scale = this.MinPaperScale
        }
        else if(scale>this.MaxPaperScale) {
            scale = this.MaxPaperScale
        }

        this.paper.viewbox.scale = scale
        this.paper.viewbox.x = pivotX - px/scale
        this.paper.viewbox.y = pivotY - py/scale
        
        this.updateViewBox()
        
        if(!slient)
            this.emit("zoom", scale, oldScale)
    }

    public setActiveFigure(figure?: FigureBase) {
        if(this.activeFigure && this.activeFigure!=figure) {
            this.activeFigure.actived = false
            this.emit("unactive", this.activeFigure)
            this.activeFigure.emit("unactive", figure)
            this.activeFigure = undefined
        }
        
        if(figure) {
            this.activeFigure = figure
            this.activeFigure.actived = true
            this.emit("active", figure)
            figure.emit("active", this.activeFigure)
        }
    }

    public loadFromModel(model: AppModel) {
        // console.log(model)

        this.model = model

        let jsonPartFigures = (model.json?.graph?.parts) || {}
        let jsonWireFigures = (model.json?.graph?.wires) || {}

        // 加载零件的 figure
        for(let part of model.lstParts) {
            this.createPartFigure(part)
            // this.autoPlacement(part)

            if(part.figure) {
                if(jsonPartFigures[part.uuid]) {
                    part.figure.unserialize(jsonPartFigures[part.uuid])
                }
                else {
                    // 新模型主机零件的默认位置(居中)
                    if(model.masterPart==part){
                        part.figure.moveTo((PaperWidth-part.figure.width)/2, (PaperHeight-part.figure.height)/2)
                    }
                }
            }
        }

        // 加载模型里的 wire
        for(let wire of model.lstWires) {
            if(!wire.figure) {
                new WireFigure(this, wire)
            }

            if(wire.figure && jsonWireFigures[wire.uuid]) {
                wire.figure.unserialize(jsonWireFigures[wire.uuid])
            }
        }

        if(model.json?.graph?.viewbox) {
            this.paper.viewbox.x = model.json.graph.viewbox.x
            this.paper.viewbox.y = model.json.graph.viewbox.y
            this.paper.viewbox.scale = model.json.graph.viewbox.scale
            this.updateViewBox()
        }

        // 新模型默认视口
        else {
            this.paper.viewbox.x = (PaperWidth-this.paperElement.clientWidth)/2
            this.paper.viewbox.y = (PaperHeight-this.paperElement.clientHeight)/2
            this.paper.viewbox.scale = 1
            this.updateViewBox()
        }

        model.on("part.add", (part: PartBase)=>{
            this.createPartFigure(part)
            // this.autoPlacement(part)
            this.loadWireFromPart(part)
        })
        model.on("part.remove", (part: PartBase)=>{
            part.figure && part.figure.remove()
        })
        model.on("wire.connect", (wire: Wire)=>{
            new WireFigure(this, wire)
        })
        model.on("wire.disconnect", (wire: Wire)=>{
            wire.figure && wire.figure.remove()
        })
    }

    public createPartFigure(part: PartBase): PartFigure {
        let clazz: any = mapPartFigureClasses[part.constructor.name] || PartFigure
        if(!part.figure) {
            let figure = new clazz(this, part)
            part.figure = figure
        }
        return (part.figure as any)
    }

    public loadWireFromPart(part: PartBase) {
        // 加载 part 上的 wire
        part.eachWire((wire: Wire)=>{
            if(!wire.figure) {
                new WireFigure(this, wire)
            }
        })
    }

    public coords(x:number, y:number) {
        return {
            x: this.paper.viewbox.x + (x - this.paperElement.offsetLeft)/this.paper.viewbox.scale ,
            y: this.paper.viewbox.y + (y - this.paperElement.offsetTop)/this.paper.viewbox.scale
        }
    }

    public removeFigre(figure: FigureBase|undefined) {
        if(!figure) return 
        if( figure instanceof WireFigure) {
            this.model.disconnectWire(figure.target)
        }
        else if( figure instanceof PartFigure) {
            this.model.removePart(figure.target)
        }

        if( figure == this.activeFigure ) {
            this.setActiveFigure(undefined)
        }
    }

    public autoPlacement(part: PartBase) {
        if(!part.figure)
            return
        
        let placement = (scope: any): boolean => {
            if(!part.figure)
                return false

            let columns = Math.ceil((scope.w - bbox.width - scope.x)/snap)
            let rows = Math.ceil((scope.h - bbox.height - scope.y)/snap)
    
            for(let r=0;r<rows;r++) {
    
                for(let c=0;c<columns;c++) {
    
                    let tmp = {
                        x: scope.x + c*snap ,
                        y: scope.y + r*snap ,
                        x2: scope.x + c*snap + bbox.width ,
                        y2: scope.y + r*snap + bbox.height ,
                    } as any
    
                    let collided = this.model.eachPart((p: PartBase)=>{
                        if(!p.figure || !part.figure || p.figure==part.figure)
                            return
                        if( testBBox(p.figure.bbox(), tmp) ) {
                            return true
                        }
                    })
    
                    if(!collided) {
                        part.figure.moveTo(tmp.x, tmp.y)
                        return true
                    }
                }
            }

            return false
        }
        
        let bbox = part.figure.bbox()
        let snap = SnapStepPart*PPM 

        // 在 viewbox 范围内找地方
        if( placement({
            w: this.paperElement.clientWidth/this.paper.viewbox.scale ,
            h: this.paperElement.clientHeight/this.paper.viewbox.scale ,
            x: this.paper.viewbox.x,
            y: this.paper.viewbox.y
        }) ) {
            return
        }

        // 在 viewbox 外找地方
        placement({
            w: PaperWidth ,
            h: PaperHeight ,
            x: 0 ,
            y: 0
        })
    }

    public serialize() {
        if(!this.model) {
            return
        }

        let json: any = {
            viewbox: {
                x: this.paper.viewbox.x ,
                y: this.paper.viewbox.y ,
                scale: this.paper.viewbox.scale ,
            }
        }

        // 零件 figure
        if( this.model.lstParts.length ) {
            json.parts = {}
            this.model.eachPart(part=>{
                if(!part.figure)
                    return
                json.parts[part.uuid] = part.figure.serialize()
            })
        }

        // 连线 figure
        if( this.model.lstWires.length ) {
            json.wires = {}
            this.model.eachWire(wire=>{
                if(!wire.figure)
                    return
                json.wires[wire.uuid] = wire.figure.serialize()
            })
        }

        return json
    }

    public cleanAll() {

        if(this.model) {
            // 清理所有 part/stud 的 figure 对象
            this.model.eachPart(part=>{
                if(part.figure) {
                    part.figure.remove()
                }
            })
            
            // 清理连线 的 figure 对象
            this.model.eachWire(wire=>{
                if(wire.figure) {
                    wire.figure.remove()
                }
            })
        }

        this.paper.clear()
        this.paper.remove()
    }

    public destroy() {
        
        $(this.paperElement).off('mousewheel', this.mousewheelHandle)
        $(this.paperElement).off('touchmove', this.touchmoveHanlde)

        this.cleanAll()
        super.destroy()
    }
}



